import Dep, { popTarget, pushTarget } from "./dep";

let id = 0;

// 当创建渲染watcher的时候，会把当前渲染的watcher放到 Dep。target上
// 调用_render() 会取值，走到get上

class Watcher {
  // 不同组件有不同的watcher 目前只有一个 渲染根实例的
  constructor(vm, exprOrFn, options, cb) {
    this.id = id++;

    // 渲染一个watcher
    this.renderWatcher = options;

    // getter意味着调用这个函数可以发生取值操作
    if (typeof exprOrFn === "string") {
      // 去实例上取相对应的函数
      this.getter = function () {
        return vm[exprOrFn];
      };
    } else {
      this.getter = exprOrFn;
    }

    // 后续实现计算属性，和一些清理工作需要用到
    this.deps = [];

    this.depsId = new Set();

    // 获取是否需要懒加载的布尔值
    this.lazy = options.lazy;
    this.dirty = this.lazy;
    this.vm = vm;
    // 如果为真则不执行，为假才执行
    this.value = this.lazy ? undefined : this.get();

    this.cb = cb;
    this.user = options.user; // 标识是否是用户自己的watcher
  }

  evaluate() {
    // 获取用户的返回值，并且标识为脏
    this.value = this.get();
    this.dirty = false;
  }

  get() {
    // 静态属性只有一份
    pushTarget(this);
    // 会去vm上取值
    let value = this.getter.call(this.vm);
    // 渲染完毕后清空
    popTarget();
    // 返回给evaluate函数使用
    return value;
  }

  depend() {
    let i = this.deps.length;

    while (i--) {
      // 让计算属性watcher也收集渲染watcher
      this.deps[i].depend();
    }
  }

  addDep(dep) {
    let id = dep.id;

    if (!this.depsId.has(id)) {
      this.deps.push(dep);
      this.depsId.add(id);
      dep.addSub(this); // watcher已经记住dep而且去重了，此时让dep也记住watcher
    }
  }

  update() {
    if (this.lazy) {
      // 计算属性依赖的值变化了，计算属性就是脏值了
      this.dirty = true;
    } else {
      queueWatcher(this);
      // 重新渲染
      // this.get();
    }
  }

  run() {
    let oldValue = this.value;
    let newValue = this.get();
    if (this.user) {
      // 用户自己的watcher
      this.cb.call(this.vm, newValue, oldValue);
    }
  }
}

let queue = [];
let has = {};
let pending = false;

function flushSchedulerQueue() {
  let flushQueue = queue.slice(0);
  queue = [];
  has = {};
  pending = false;
  flushQueue.forEach((q) => q.run());
}

function queueWatcher(watcher) {
  const id = watcher.id;
  if (!has[id]) {
    queue.push(watcher);
    has[id] = true;

    if (!pending) {
      nextTick(flushSchedulerQueue, 0);
      pending = true;
    }
  }
}

let callbacks = [];
let waiting = false;

function flushCallbacks() {
  waiting = false;
  let cbs = callbacks.slice(0);
  callbacks = [];
  // 按照顺序执行nextTick内容方法函数
  cbs.forEach((cb) => cb());
}

// nextTick中不是直接使用定时器API，而是采用优雅降级的方法，放到队列中是同步，单独开是异步
// 内部先采用的是promise（ie不兼容） MutationObserver（H5的API） 都不兼容再采用 setImmediate（ie专享），最后都不兼容就采用 定时器
let timeFn;
if (Promise) {
  timeFn = () => {
    Promise.resolve().then(flushCallbacks);
  };
} else if (MutationObserver) {
  // 这里传入的回调是异步执行的
  let observe = new MutationObserver(flushCallbacks);
  let textNode = document.createTextNode(1);
  observe.observe(textNode, {
    characterData: true,
  });
  timeFn = () => {
    textNode.textContent = 2;
  };
} else if (setImmediate) {
  timeFn = () => {
    setImmediate(flushCallbacks);
  };
} else {
  timeFn = () => {
    setTimeout(flushCallbacks);
  };
}

export function nextTick(cb) {
  // 先内部还是先用户？先用户。维护nextTick中的callback方法
  callbacks.push(cb);
  if (!waiting) {
    timeFn();
    waiting = true;
  }
}

// 需要给每个属性添加一个dep，目的就是收集watcher
// 一个视图有多个属性，也就是n个dep对应一个watcher。同样的，一个属性在多个视图都有，因此1个dep对应多个watcher

export default Watcher;
